﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;
using Senthil.Mobile.WhereAmI;
using Rhino.Mocks.Interfaces;

namespace wamilibtest
{
    [TestClass]
    public class RouteTrackerTests
    {
        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        [TestMethod]
        public void TestInitialization()
        {
            ILocationChangeNotifier notifier = mocker.CreateMock<ILocationChangeNotifier>();

            using (mocker.Record())
            {
                Expect.Call(() => notifier.LocationChanged += null).IgnoreArguments();
                Expect.Call(notifier.Initialize);
            }
            using (mocker.Playback())
            {
                RouteTracker tracker = new RouteTracker(new Route(), notifier);
                tracker.Initialize();
            }
        }

        [TestMethod]
        public void TestLocationChangeToUnknownLocation()
        {
            const string locationName = "India";
            route = CreateStockRoute();
            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += new EventHandler<LocationChangedEventArgs>(subscriber.HandleLocationChange);

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(new Location(locationName, false), null, null, null));
            }
            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs(locationName, ""));
            }
        }

        [TestMethod]
        public void TestLocationChangeToUnknownLocationLastUnknownLocationKnown()
        {
            const string initialLocationName = "L1";
            const string locationName = "India";

            route = CreateStockRoute();
            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += new EventHandler<LocationChangedEventArgs>(subscriber.HandleLocationChange);

            Location expectedLocation;
            route.TryGetFirstLocationWithNameAndCellTowerId(initialLocationName, "", out expectedLocation);
            LocationGroup expectedNSLG = route.GetNextMajorLocationGroup(expectedLocation);
            IEnumerable<LocationGroup> expectedFurtherLocationGroups = route.GetLocationGroupsAfter(expectedLocation);

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(expectedLocation, expectedNSLG, expectedFurtherLocationGroups, null));
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(new Location(locationName, false), expectedNSLG, expectedFurtherLocationGroups, expectedLocation));
            }
            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs(initialLocationName, ""));
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs(locationName, ""));
            }
        }

        [TestMethod]
        public void TestLocationChangeToKnownLocation()
        {
            const string locationName = "L1";

            route = CreateStockRoute();
            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += new EventHandler<LocationChangedEventArgs>(subscriber.HandleLocationChange);

            Location expectedLocation;
            route.TryGetFirstLocationWithNameAndCellTowerId(locationName, "", out expectedLocation);
            LocationGroup expectedNSLG = route.GetNextMajorLocationGroup(expectedLocation);
            IEnumerable<LocationGroup> expectedFurtherLocationGroups = route.GetLocationGroupsAfter(expectedLocation);

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(expectedLocation, expectedNSLG, expectedFurtherLocationGroups, null));
            }
            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs(locationName, ""));
            }
        }

        [TestMethod]
        public void TestLocationChangeToLastLocation()
        {
            const string locationName = "L2";

            route = CreateStockRoute();
            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += new EventHandler<LocationChangedEventArgs>(subscriber.HandleLocationChange);

            Location expectedLocation;
            route.TryGetFirstLocationWithNameAndCellTowerId(locationName, "", out expectedLocation);
            LocationGroup expectedNSLG = route.GetNextMajorLocationGroup(expectedLocation);
            IEnumerable<LocationGroup> expectedFurtherLocationGroups = route.GetLocationGroupsAfter(expectedLocation);

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(expectedLocation, expectedNSLG, expectedFurtherLocationGroups, null));
            }
            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs(locationName, ""));
            }
        }

        [TestMethod]
        public void TestETAChange()
        {
            const string locationName = "L1";

            route = CreateStockRoute();
            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.TimeChanged += subscriber.HandleTimeChanged;

            Location loc;
            route.TryGetFirstLocationWithNameAndCellTowerId(locationName, "", out loc);
            TimeSpan destinationTimeSpan = route.GetAverageTimeTaken(loc, route.DestinationGroup.Locations);
            TimeSpan? timeToNSG = route.GetAverageTimeTaken(loc, route.GetNextMajorLocationGroup(loc).Locations);

            using (mocker.Record())
            {
                subscriber.HandleTimeChanged(tracker, new TimeChangedEventArgs(destinationTimeSpan, timeToNSG));
            }
            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs(locationName, ""));
            }
        }

        [TestMethod]
        public void TestETAChangeToUnknownLocation()
        {
            const string locationName = "India";

            route = CreateStockRoute();
            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.TimeChanged += subscriber.HandleTimeChanged;

            using (mocker.Record())
            {
            }
            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs(locationName, ""));
            }
        }

        [TestMethod]
        [ExpectedException(typeof(InvalidOperationException), "There is no last known location to calculate time span.")]
        public void TestGetTimeSpanWithNoKnownLocation()
        {
            const string locationName = "L1";

            route = CreateStockRoute();
            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            Location location;
            route.TryGetFirstLocationWithNameAndCellTowerId(locationName, "", out location);
            TimeSpan timeToDestination = route.GetAverageTimeTaken(location, route.DestinationGroup.Locations);

            Assert.IsFalse(tracker.CanCalculateTimeSpan());
            Assert.AreEqual(timeToDestination, tracker.GetTimeSpanFor(route.DestinationGroup));
        }

        [TestMethod]
        public void TestGetTimeSpan()
        {
            const string locationName = "L1";

            route = CreateStockRoute();
            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += new EventHandler<LocationChangedEventArgs>(subscriber.HandleLocationChange);

            Location expectedLocation;
            route.TryGetFirstLocationWithNameAndCellTowerId(locationName, "", out expectedLocation);
            LocationGroup expectedNSLG = route.GetNextMajorLocationGroup(expectedLocation);
            IEnumerable<LocationGroup> expectedFurtherLocationGroups = route.GetLocationGroupsAfter(expectedLocation);

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(expectedLocation, expectedNSLG, expectedFurtherLocationGroups, null));
            }
            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs(locationName, ""));
            }
        }

        [TestMethod]
        public void TestLocationChangeNoBroadcastCellTowerIdFirstLocation()
        {
            LocationGroup group1 = new LocationGroup("LG1");
            LocationGroup group2 = new LocationGroup("LG2");

            Location l1 = new Location("L1", "1234");
            Location l2 = new Location("L2", "5678");

            group1.AddLocation(l1);
            group2.AddLocation(l2);

            route = new Route("TestRoute");

            route.AddLocationGroup(group1);
            route.AddLocationGroup(group2);

            route.AddNextRoutePoint(l1, TimeSpan.Zero);
            route.AddNextRoutePoint(l2, TimeSpan.FromSeconds(5));

            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += subscriber.HandleLocationChange;

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(l1, null, route.GetLocationGroupsAfter(l1), null));
            }

            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs("", "1234"));
            }
        }

        [TestMethod]
        public void TestLocationChangeNoBroadcastCellTowerId()
        {
            LocationGroup group1 = new LocationGroup("LG1");
            LocationGroup group2 = new LocationGroup("LG2");

            Location l1 = new Location("L1", "1234");
            Location l2 = new Location("L2", "5678");

            group1.AddLocation(l1);
            group2.AddLocation(l2);

            route = new Route("TestRoute");

            route.AddLocationGroup(group1);
            route.AddLocationGroup(group2);

            route.AddNextRoutePoint(l1, TimeSpan.Zero);
            route.AddNextRoutePoint(l2, TimeSpan.FromSeconds(5));

            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += subscriber.HandleLocationChange;

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(l2, null, route.GetLocationGroupsAfter(l2), null));
            }

            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs("", "5678"));
            }
        }

        [TestMethod]
        public void TestLocationChangeUnknownCellTowerIdApproximateMatchInitialLocationGroup()
        {
            LocationGroup group1 = new LocationGroup("LG1");
            LocationGroup group2 = new LocationGroup("LG2");

            Location l1 = new Location("L1", "1234_4321");
            Location l2 = new Location("L2", "5678_4321");

            group1.AddLocation(l1);
            group2.AddLocation(l2);

            route = new Route("TestRoute");

            route.AddLocationGroup(group1);
            route.AddLocationGroup(group2);

            route.AddNextRoutePoint(l1, TimeSpan.Zero);
            route.AddNextRoutePoint(l2, TimeSpan.FromSeconds(5));

            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += subscriber.HandleLocationChange;

            Location unknownLocation = new Location("", "234_4321", false);
            unknownLocation.LocationGroup = group1;

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(unknownLocation, null, route.GetLocationGroupsAfter(l1), null));
            }

            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs("", "234_4321"));
            }
        }

        [TestMethod]
        public void TestLocationChangeUnknownCellTowerIdApproximateMatchMiddleLocationGroup()
        {
            LocationGroup group1 = new LocationGroup("LG1");
            LocationGroup group2 = new LocationGroup("LG2");
            LocationGroup group3 = new LocationGroup("LG3");

            Location l1 = new Location("L1", "1234_1111");
            Location l2 = new Location("L2", "5678_2222");
            Location l3 = new Location("L3", "8_3333");

            group1.AddLocation(l1);
            group2.AddLocation(l2);
            group3.AddLocation(l3);

            route = new Route("TestRoute");

            route.AddLocationGroup(group1);
            route.AddLocationGroup(group2);
            route.AddLocationGroup(group3);

            route.AddNextRoutePoint(l1, TimeSpan.Zero);
            route.AddNextRoutePoint(l2, TimeSpan.FromSeconds(5));
            route.AddNextRoutePoint(l3, TimeSpan.FromSeconds(5));

            tracker = new RouteTracker(route, notifier);
            tracker.Initialize();

            tracker.LocationChanged += subscriber.HandleLocationChange;

            Location unknownLocation = new Location("", "1234_2222", false);
            unknownLocation.LocationGroup = group2;

            using (mocker.Record())
            {
                subscriber.HandleLocationChange(tracker, new LocationChangedEventArgs(unknownLocation, null, route.GetLocationGroupsAfter(l2), null));
            }

            using (mocker.Playback())
            {
                eventRaiser.Raise(tracker, new LocationChangeNotificationEventArgs("", "1234_2222"));
            }
        }

        ILocationChangeNotifier notifier;
        IEventRaiser eventRaiser;
        IEventSubscriber subscriber;
        Route route;
        RouteTracker tracker;
        MockRepository mocker;

        [TestInitialize]
        public void Setup()
        {
            mocker = new MockRepository();
            notifier = mocker.Stub<ILocationChangeNotifier>();
            eventRaiser = null;
            subscriber = mocker.CreateMock<IEventSubscriber>();

            notifier.LocationChanged += null;
            eventRaiser = LastCall.IgnoreArguments().GetEventRaiser();
        }


        private Route CreateStockRoute()
        {
            Route route = new Route("TestRoute");

            LocationGroup lg1 = new LocationGroup("LG1");
            LocationGroup lg2 = new LocationGroup("LG2", true);

            route.AddLocationGroup(lg1);
            route.AddLocationGroup(lg2);

            Location l1 = route.Associate(lg1, "L1");
            Location l2 = route.Associate(lg2, "L2");

            route.AddNextRoutePoint(l1, TimeSpan.Zero);
            route.AddNextRoutePoint(l2, TimeSpan.FromSeconds(5));

            return route;
        }

        private Route CreateRouteNoSG()
        {
            Route route = new Route("TestRoute");

            LocationGroup lg1 = new LocationGroup("LG1");
            LocationGroup lg2 = new LocationGroup("LG2");

            route.AddLocationGroup(lg1);
            route.AddLocationGroup(lg2);

            Location l1 = route.Associate(lg1, "L1");
            Location l2 = route.Associate(lg2, "L2");

            route.AddNextRoutePoint(l1, TimeSpan.Zero);
            route.AddNextRoutePoint(l2, TimeSpan.FromSeconds(5));

            return route;
        }


        public interface IEventSubscriber
        {
            void HandleLocationChange(object sender, LocationChangedEventArgs e);
            void HandleTimeChanged(object sender, TimeChangedEventArgs e);
        }

    }
}
